﻿using System.Threading;
namespace System.Web
{
    internal class NetAspNetSynchronizationContext : SynchronizationContext
    {
        private NetHttpApplication _application;
        private bool _disabled;
        private Exception _error;
        private bool _invalidOperationEncountered;
        private WaitCallback _lastCompletionWorkItemCallback;
        private int _pendingCount;
        private bool _syncCaller;

        internal NetAspNetSynchronizationContext(NetHttpApplication application)
        {
            _application = application;
        }

        private void CallCallback(SendOrPostCallback callback, object state)
        {
            if (_syncCaller)
                CallCallbackPossiblyUnderLock(callback, state);
            else
                lock (_application)
                    CallCallbackPossiblyUnderLock(callback, state);
        }

        private void CallCallbackPossiblyUnderLock(SendOrPostCallback callback, object state)
        {
            NetHttpApplication.ThreadContext context = null;
            try
            {
                context = _application.OnThreadEnter();
                try { callback(state); }
                catch (Exception exception) { _error = exception; }
            }
            finally
            {
                if (context != null)
                    context.Leave();
            }
        }

        internal void ClearError()
        {
            _error = null;
        }

        public override SynchronizationContext CreateCopy()
        {
            return new NetAspNetSynchronizationContext(_application) { _disabled = _disabled, _syncCaller = _syncCaller };
        }

        internal void Disable()
        {
            _disabled = true;
        }

        internal void Enable()
        {
            _disabled = false;
        }

        public override void OperationCompleted()
        {
            if ((!_invalidOperationEncountered && (!_disabled || (_pendingCount != 0))) && ((Interlocked.Decrement(ref _pendingCount) == 0) && (_lastCompletionWorkItemCallback != null)))
            {
                var callBack = _lastCompletionWorkItemCallback;
                _lastCompletionWorkItemCallback = null;
                ThreadPool.QueueUserWorkItem(callBack);
            }
        }

        public override void OperationStarted()
        {
            if (_invalidOperationEncountered || (_disabled && (_pendingCount == 0)))
            {
                _invalidOperationEncountered = true;
                throw new InvalidOperationException("Async_operation_disabled");
            }
            Interlocked.Increment(ref _pendingCount);
        }

        public override void Post(SendOrPostCallback callback, object state)
        {
            CallCallback(callback, state);
        }

        internal void ResetSyncCaller()
        {
            _syncCaller = false;
        }

        public override void Send(SendOrPostCallback callback, object state)
        {
            CallCallback(callback, state);
        }

        internal void SetLastCompletionWorkItem(WaitCallback callback)
        {
            _lastCompletionWorkItemCallback = callback;
        }

        internal void SetSyncCaller()
        {
            _syncCaller = true;
        }

        internal bool Enabled
        {
            get { return !_disabled; }
        }

        internal Exception Error
        {
            get { return _error; }
        }

        internal int PendingOperationsCount
        {
            get { return _pendingCount; }
        }
    }
}
